home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1994 November / macformat-018.iso / Utility Spectacular / Text / SpeakingBBEdit folder / SpeakSentence.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-10-05  |  16.9 KB  |  614 lines  |  [TEXT/R*ch]

  1. /*******************************************************************************************
  2.                                                 By R. Mark Fleming
  3.                                                 Copyright (c) 1993, All Rights Reserved.
  4.                                                 
  5. Two new BBEdit Extensions for speaking the selected text using Apple new Speach Manager.  
  6.  
  7. 1)    Speaking
  8.  
  9.     Simple extension that use Apple's new speach manager to 'Talk' the selected text.
  10.                   
  11. 2)    Speaking with Dialog
  12.  
  13.     An extensiont that use Apple's new speach manager to 'Talk' the selected text.
  14.  
  15.     The use is presented with a dialog box with some limited control over how text is spoken.
  16.  
  17.     * You can ask it to spell the text or speak the text.
  18.     * You can use the Talk, Stop, Pause, and Continue buttons to control and replay the text.
  19.     * The exit button is used to return to BBEdit.
  20.  
  21. Things to do...
  22. * Fix StdTTS component to allow user to select voice, rate, pitch.
  23. * Save and restore voice setting using BBEdit's preference callbacks
  24. * Disable Spell/Talk check while talking or make it stop and continue correctly.
  25.  
  26. History...
  27. Version 1.00    Release "Speaking sentences" & "Speaking" module.
  28. Version 1.10    Fix bug were no female voice is installed (error -244). 
  29.                 Added tracking of text being spoken.
  30.                 Merged both modules into on file: "Speaking".
  31.                 If option key is depressed, the default is change to spelling selected text.
  32. Version 1.11    Fix bug when selected text is scrolled into view (messy...) 
  33.                 Add correct version resource so GetInfo... from finder will display version number.
  34. Version 1.20    Cleaned up source code to be included with modules.  
  35.                 Feel free to enhance these modules...but give credit were credit is due.
  36.                 (and send me updated source, so I can add it to any changes I do!).
  37. Version 1.21    Change all NewHandle() calls to callback-Allocate() calls.
  38.                 Free memory allocated when error occures when allocating speech channel.
  39.                 #ifdef StackDebug code to check stack space problem (OS Error 28).
  40.                 
  41. Known Problems:
  42. *    System Error 28 - Stack has moved into Application heap occurs on several machines.
  43.                       I have been unable to prevent this, during the process the speech manager
  44.                       is using to start up async speech, this stack is used up.
  45.                           
  46. Cheers, Mark Fleming
  47.  
  48. 521 Albert Street, 
  49. Kingston, Ontario,
  50. Canada, K7K 4M5
  51.  
  52. Phone: (613) 544-3563           Fax:   (613) 544-7499
  53. AppleLink: CDA0621              InterNet: markf@Post.QueensU.CA 
  54. ** The Author of NetDoctor,  a Lab and Network Maintainer Package **
  55.  
  56. #define StackDebug
  57. *******************************************************************************************/
  58. #define DialogDisplayed 1    // if define set CODE Resource name to "Speaking with Dialog" id=128
  59.                             // if not define set it to "Speaking" id=129
  60.  
  61.  
  62. #include "ExternalInterface.h"
  63. #include "DialogUtilities.h"
  64. #include <SetupA4.h>
  65. #include <Desk.h>
  66. #include <Memory.h>
  67. #include <Components.h>
  68. #include <Speech.h>
  69. #include <StdTTS.h>
  70. #include <OSUtils.h>
  71. #include <ToolUtils.h>
  72. #include <BDC.h>
  73.  
  74. #include <GestaltEqu.h>
  75. #pragma parameter __D0 Gestalt(__D0,__A1)
  76. pascal OSErr Gestalt(OSType selector,long *response)    = {0xA1AD,0x2288}; 
  77. #include <Folders.h>
  78.  
  79. enum {
  80.     iTalk =1,
  81.     iStop,
  82.     iCont,
  83.     iPause,
  84.     
  85.     iVoice,
  86.     IPitch,
  87.     iRate,
  88.     iCharMode,
  89.     iExit
  90.     };
  91.     
  92. struct RefSpeak {                /* Speech Channel Ref set to point to this */
  93.         ExternalCallbackBlock *callbacks;
  94.         WindowPtr w;
  95.         DialogPtr d;
  96.         long    selStart;
  97.         long    selEnd;
  98.         long    firstChar;
  99.         Boolean    done, spellit;
  100.         SpeechStatusInfo SInfo;
  101.         SpeechChannel channel;
  102.         StdTTSParams    TTSParams;
  103.         };
  104.  
  105.                         
  106. Boolean            SpeechInstalled(ExternalCallbackBlock *callbacks);
  107. ComponentResult    DoSetTTS(struct RefSpeak *RefSpeechStorage);
  108. OSErr            SetTalkOptions(struct RefSpeak *RefSpeechStorage);
  109. OSErr            stop_speech(SpeechChannel channel);
  110.  
  111. Boolean OptionDown(void);
  112. Boolean CommandDown(void);
  113.  
  114. pascal void MyWordCallback (SpeechChannel chan, 
  115.                         long refCon, long wordPos, short wordLen);
  116.  
  117. static pascal Boolean filter(DialogPtr d, EventRecord *event, short *item);
  118. void SetDialogButtons(register DialogPtr d);
  119. void HandleDialog(Handle text, long selStart, Size size);
  120.     
  121. static struct RefSpeak *gRefSpeech;
  122.  
  123. /*******************************************************************************************/
  124.  
  125. Boolean SpeechInstalled(ExternalCallbackBlock *callbacks)
  126. {    OSErr err;
  127.     long selStart;
  128.     
  129.     err = Gestalt(gestaltSpeechAttr, &selStart);
  130.     if (err == gestaltUndefSelectorErr || err != noErr) {    // requires that the Speech Manager be installed.
  131.         callbacks->ReportOSError(err);
  132.         
  133.         return false;
  134.         }
  135.     if (!(selStart && (1L << gestaltSpeechMgrPresent))) {    // requires that the Speech Manager be installed. 
  136.         callbacks->ReportOSError(gestaltUndefSelectorErr);
  137.         return false;
  138.         }
  139.     return true;
  140. }    
  141.  
  142. /******************************************************************************************
  143.  
  144.     Use Standard Voice Selection Component if installed to select voice parameters...
  145.     
  146.     This is a modified version of "Speech Media Handler.sea" routine to select voice information.
  147.     I added saving/resoring GrafPort(), & default voice information if no component is present.
  148. */
  149. #define kDefaultVoice 1
  150. ComponentResult DoSetTTS(struct RefSpeak *RefSpeechStorage)
  151. {    Component                 ttsComponent;
  152.     ComponentInstance         ttsCInstance;
  153.     ComponentDescription     ttsDesc;
  154.     Point                     where = {0,0};
  155.     OSErr                     err;
  156.     ComponentResult            result;
  157.  
  158.     ttsDesc.componentType             = stdTextToSpeechComponentType;
  159.     ttsDesc.componentSubType         = 0;
  160.     ttsDesc.componentManufacturer     = 'appl';
  161.     ttsDesc.componentFlags             = 0;
  162.     ttsDesc.componentFlagsMask         = 0;
  163.     
  164.     ttsComponent = FindNextComponent(nil, &ttsDesc); 
  165.     
  166.     if (0 &&                                /* Disable StdTTS component since I did not get it to work */
  167.         CommandDown() && ttsComponent) {            
  168.         GrafPtr save_port;
  169.         GetPort(&save_port);
  170.  
  171.         err = GetIndVoice(kDefaultVoice, &RefSpeechStorage->TTSParams.curVoice.voice);
  172.         gRefSpeech->callbacks->ReportOSError(err);
  173.         
  174.         ttsCInstance     = OpenComponent(ttsComponent);
  175.         result             = StdSpeechDialog(ttsCInstance, &RefSpeechStorage->TTSParams, &where);
  176.         err             = CloseComponent(ttsCInstance);
  177.         
  178.         SetPort(save_port);
  179.     } else {                    /* Select Voice info... */
  180.     
  181.         gRefSpeech->TTSParams.curVoice.voice.creator = 'gala';        /* try to use "Female voice" = 12 */
  182.         gRefSpeech->TTSParams.curVoice.voice.id = (long)12;
  183.         err = GetVoiceDescription(&gRefSpeech->TTSParams.curVoice.voice, 
  184.                                   &gRefSpeech->TTSParams.curVoice, sizeof(VoiceDescription));
  185.                                   
  186.         if (err == voiceNotFound) {                                    /* Could find selected voice, Use 1st voice. */
  187.             err = GetIndVoice (kDefaultVoice, &gRefSpeech->TTSParams.curVoice.voice);
  188.             gRefSpeech->callbacks->ReportOSError(err);
  189.             
  190.             err = GetVoiceDescription(&gRefSpeech->TTSParams.curVoice.voice, 
  191.                                       &gRefSpeech->TTSParams.curVoice, sizeof(VoiceDescription));
  192.             }
  193.         result = err;
  194.         }
  195.     
  196. return result;
  197. }    /* End of () */
  198.  
  199. /******************************************************************************************
  200.  
  201.     1)    If option key is press, select Spell Literals, else use normal talking
  202.     
  203.     2)    Install reference to global variable for this channel
  204.     
  205.     3)    Install word callback routine to track text.
  206.     
  207. */
  208.  
  209. OSErr    SetTalkOptions(register struct RefSpeak *RefSpeechStorage)
  210. {    OSType t;
  211.     register OSErr err;
  212.     
  213.     t = (!OptionDown()) ?  'NORM' : 'LTRL';            /* Spell letters or talk ... */
  214.     
  215.     err = SetSpeechInfo (RefSpeechStorage->channel, soCharacterMode, &t);
  216.     if (err != noErr) {
  217.             RefSpeechStorage->callbacks->ReportOSError(err);
  218.             }
  219.     
  220.     err = SetSpeechInfo (RefSpeechStorage->channel, soRefCon, gRefSpeech);
  221.     if (err != noErr) {
  222.             RefSpeechStorage->callbacks->ReportOSError(err);
  223.             }
  224.             
  225.     err = SetSpeechInfo (RefSpeechStorage->channel, soWordCallBack, MyWordCallback);
  226.     if (err != noErr) {
  227.             RefSpeechStorage->callbacks->ReportOSError(err);
  228.             }
  229.     
  230.     return err;    
  231. }    /* End of () */
  232.  
  233. /******************************************************************************************
  234.  
  235.     1)    Stop the current speaking at the end of the current word, wait until this has been
  236.         done before returning.  Call SystemTask() to give background tasks chance to do things.
  237. */
  238.  
  239. OSErr stop_speech(SpeechChannel channel)
  240. {    OSErr err;
  241.     
  242.     err = StopSpeechAt(channel, kEndOfWord);
  243.     if (err != noErr) SysBeep(20);
  244.     
  245.     while (SpeechBusy() > 0)
  246.         SystemTask();
  247.     
  248.     return err;
  249. }    /* End of () */
  250.  
  251. /*********************************************************************************************
  252.     Enables word callbacks. The callback routine is invoked for each word generated by the 
  253.     speech synthesizer just before the word is actually spoken. 
  254.      
  255.     This callback can be disabled by passing a nil ProcPtr in the speechInfo parameter.  
  256.     The API label for this selector is:    soWordCallBack.
  257. */
  258.  
  259. pascal void MyWordCallback (SpeechChannel chan, 
  260.                         long refCon, long wordPos, short wordLen)
  261. {    struct RefSpeak *RefSpeak;
  262.     GrafPtr save_port;
  263.     long    stackSp;
  264.     
  265.     stackSp = StackSpace();    
  266.  
  267. #ifdef StackDebug
  268.     {    Str15     tmpString;
  269.     
  270.         NumToString(stackSp, tmpString);
  271.         DebugStr(tmpString);
  272.         }
  273. #endif
  274.  
  275.     if (stackSp > 12000L) {
  276.     
  277.         GetPort(&save_port);
  278.         RefSpeak = (struct RefSpeak *) refCon;    /* Get Address of my globals */
  279.         SetPort(RefSpeak->w);
  280.                                                 /* Hilight next work to be spoken */
  281.         RefSpeak->callbacks->SetSelection(RefSpeak->selStart + wordPos, RefSpeak->selStart + wordPos + wordLen, -1);
  282.         
  283.         SetPort(save_port);
  284.         }    /* End if Stack Space */
  285.         
  286. }    /* End of () */
  287.  
  288. Boolean OptionDown(void)
  289. {    KeyMap k;
  290.     
  291.     GetKeys(&k);
  292.     return (BitTst(&k, 61));
  293.     
  294.     /* true if option */
  295. }    /* End of ModifierDown() */
  296.  
  297. Boolean CommandDown(void)
  298. {    KeyMap k;
  299.     
  300.     GetKeys(&k);
  301.     return (BitTst(&k, 63));
  302.     
  303.     /* true if Command */
  304. }    /* End of CommandDown() */
  305.  
  306. #ifdef DialogDisplayed
  307.  
  308. /******************************************************************************************
  309.  
  310.     1)    Update dialog buttons relative to what is currently talking, and call BBEdit's
  311.         window filter routine to do the rest of the need things.
  312. */
  313.  
  314. static pascal Boolean filter(DialogPtr d, EventRecord *event, short *item)
  315. {    OSErr err;
  316.     Boolean result;
  317.     long    stackSp;
  318.     
  319.     
  320.     SetUpA4();
  321.     
  322.     stackSp = StackSpace();    
  323.     
  324.     err = GetSpeechInfo(gRefSpeech->channel, soStatus, &gRefSpeech->SInfo);
  325.     
  326.     SetDialogButtons(d);
  327.     
  328.     result = gRefSpeech->callbacks->StandardFilter(d, event, item);
  329.     
  330.     RestoreA4();
  331.     
  332.     return result;
  333. }    /* End of () */
  334.  
  335. void SetDialogButtons(register DialogPtr d)
  336. {
  337.         XAbleDlgCtl(d, iCont, gRefSpeech->SInfo.outputPaused);
  338.         XAbleDlgCtl(d, iTalk, !gRefSpeech->SInfo.outputBusy);
  339.         XAbleDlgCtl(d, iStop, gRefSpeech->SInfo.outputBusy);
  340.         XAbleDlgCtl(d, iPause, !gRefSpeech->SInfo.outputPaused & gRefSpeech->SInfo.outputBusy);
  341. }    /* End of () */
  342.  
  343. /******************************************************************************************
  344.  
  345.     1)    Place a dialog on the center of the screen.
  346.     2)    start the talk the selected text
  347.     3)    Wait for user to select exit button.
  348.     
  349. */
  350.  
  351. void HandleDialog(Handle text, long selStart, Size size)    
  352. {    OSErr err;
  353.     DialogPtr d;
  354.     short item;
  355.     Ptr    TheTextPtr;
  356.     Handle copy;
  357.     
  358.     OSType t;
  359.     long temp;
  360.     
  361.     Str15 sPitch, sRate;
  362.     
  363.     long    stackSp;
  364.     
  365.     stackSp = StackSpace();    
  366.  
  367. #ifdef StackDebug
  368.     {    Str15     tmpString;
  369.     
  370.         NumToString(stackSp, tmpString);
  371.         DebugStr(tmpString);
  372.         }
  373. #endif
  374.     
  375.     GetSpeechPitch (gRefSpeech->channel, &gRefSpeech->TTSParams.pitch);
  376.     temp = FixRound(gRefSpeech->TTSParams.pitch);
  377.  
  378.     NumToString(temp, sPitch);
  379.  
  380.     GetSpeechRate (gRefSpeech->channel, &gRefSpeech->TTSParams.rate);
  381.     temp = FixRound(gRefSpeech->TTSParams.rate);
  382.     
  383.     NumToString(temp, sRate);
  384.     ParamText(gRefSpeech->TTSParams.curVoice.name, sRate, sPitch, "\p");
  385.     
  386.     d = gRefSpeech->callbacks->CenterDialog(128);
  387.     if (!d) return;
  388.     gRefSpeech->d = d;
  389.     
  390.     SetPort(d);    SelectWindow(d);
  391.     
  392.     err = GetSpeechInfo(gRefSpeech->channel, soCharacterMode, &t);
  393.     SetDlgCtl( d, iCharMode, (t =='LTRL'));                /* Don't spell text    */
  394.     
  395.     MoveHHi(text);    HLock(text);
  396.     copy = gRefSpeech->callbacks->Allocate((Size) 0, true);
  397.     if (MemError() != noErr) {
  398.         gRefSpeech->callbacks->ReportOSError(MemError());
  399.         return;
  400.         }
  401.         
  402.     err = PtrAndHand(*text + selStart, copy, size);        /* Make copy of selected text */
  403.     HUnlock(text);
  404.     if (MemError() != noErr) {
  405.         gRefSpeech->callbacks->ReportOSError(MemError());
  406.         return;
  407.         }
  408.                 
  409.     MoveHHi(copy);    HLock(copy);
  410.     TheTextPtr = *copy;
  411.     SpeakText(gRefSpeech->channel, (Ptr)TheTextPtr, size);    /* Then speak the text through the channel */
  412.     
  413.     while(gRefSpeech->done == false) {
  414.     
  415.         ModalDialog(filter, &item);
  416.         
  417.         switch(item) {
  418.         
  419.         case iTalk:        SpeakText(gRefSpeech->channel, (Ptr)TheTextPtr, size);        /* Then speak the text through the channel */
  420.             break;
  421.             
  422.         case iStop:        err = GetSpeechInfo (gRefSpeech->channel, soStatus, &gRefSpeech->SInfo);
  423.                         if (gRefSpeech->SInfo.outputPaused)
  424.                                 err = StopSpeech(gRefSpeech->channel);
  425.                         else    err = StopSpeechAt(gRefSpeech->channel, kEndOfWord);
  426.             break;
  427.             
  428.         case iCont:        err = ContinueSpeech(gRefSpeech->channel);
  429.             break;
  430.             
  431.         case iPause:    err = PauseSpeechAt(gRefSpeech->channel, kEndOfWord);
  432.             break;
  433.             
  434.         case iCharMode:
  435.             gRefSpeech->spellit = GetDlgCtl( d, item);    /* True = talk, false = spell text */
  436.             t = gRefSpeech->spellit ?  'NORM' : 'LTRL';
  437.             err = SetSpeechInfo (gRefSpeech->channel, soCharacterMode, &t);
  438.             SetDlgCtl( d, item, !GetDlgCtl( d, item));
  439.             break;
  440.             
  441.         case iExit:    if (SpeechBusy() != 0)    stop_speech(gRefSpeech->channel);
  442.                     gRefSpeech->done = true;
  443.             break;
  444.             }    /* End of Switch */
  445.  
  446.         }    /* End of While() */
  447.         
  448.     HUnlock(copy);    
  449.     DisposHandle(copy);
  450.     DisposDialog(d);
  451. }    /* End of () */
  452.  
  453. #else
  454.  
  455. /******************************************************************************************
  456.  
  457.     1)    No dialog just talk the selected text
  458.     2)    Exit when done talking or user click mouse or presses a key.
  459. */
  460.  
  461. void HandleDialog(Handle text, long selStart, Size size)    
  462. {    OSErr err;
  463.     Ptr    TheTextPtr;
  464.     EventRecord choice;
  465.     Boolean done = false;
  466.     
  467.     long    stackSp;
  468.     
  469.     stackSp = StackSpace();    
  470.  
  471. #ifdef StackDebug
  472.     {    Str15     tmpString;
  473.     
  474.         NumToString(stackSp, tmpString);
  475.         DebugStr(tmpString);
  476.         }
  477. #endif
  478.  
  479.     MoveHHi(text);    HLock(text);
  480.                 
  481.     TheTextPtr = *text + selStart;
  482.     err = SpeakText(gRefSpeech->channel, (Ptr)TheTextPtr, size);            /* Then speak the text through the channel */
  483.     
  484.     while(!done) {
  485.     
  486.         WaitNextEvent((mDownMask | keyDownMask), &choice, 15, nil);
  487.         
  488.         switch (choice.what) {
  489.             case mouseDown:
  490.             case keyDown:
  491.                 if (SpeechBusy() != 0)    stop_speech(gRefSpeech->channel);
  492.                 done = true;
  493.                 break;
  494.             }
  495.             
  496.         if (SpeechBusy() == 0)    done = true;
  497.         }    /* End of While() */
  498.         
  499.     HUnlock(text);
  500.     
  501. }
  502. #endif
  503.  
  504. /******************************************************************************************
  505.  
  506.     1)    Check if speech manager is installed
  507.     2)    check a BBEdit window exists
  508.     3)    Get the text of the BBEdit window
  509.     4)    Setup Speech manger voice channel.
  510.     5)    Talk the selected text.
  511.     6)    clean up.
  512. */
  513.  
  514. pascal void main( ExternalCallbackBlock *callbacks, WindowPtr w)
  515. {    OSErr err;
  516.     Handle text;
  517.     Size size;
  518.     Ptr    TheTextPtr;
  519.     long selStart, selEnd;
  520.     Handle h;
  521.     GrafPtr save_port;
  522. #ifdef StackDebug
  523.     long    stackSp;
  524.     Str15     tmpString;
  525. #endif
  526.  
  527.     RememberA0();    SetUpA4();
  528.     GetPort(&save_port);
  529.     
  530.     if ( callbacks->version < 2 ||
  531.         !w ||
  532.         !SpeechInstalled(callbacks)) {
  533.         SysBeep(0);
  534.         RestoreA4();            
  535.         return;                            /* Exit no Speech Manager installed */
  536.         }
  537.         
  538.     h = callbacks->Allocate((Size) sizeof(struct RefSpeak), true);
  539.     if (!h) { RestoreA4(); return; };
  540.     
  541.     MoveHHi(h); HLock(h);
  542.     gRefSpeech = (struct RefSpeak *) *h;
  543.     
  544.     text = callbacks->GetWindowContents( w );
  545. #if 1
  546.     size = GetHandleSize(text); 
  547. #else    
  548.     asm {                                /* don't need MacTrap library this way. */
  549.         movea.l    text, a0
  550.         _GetHandleSize
  551.         move.l    d0, size
  552.     }
  553. #endif
  554.  
  555.     callbacks->GetSelection(&selStart, &selEnd, &gRefSpeech->firstChar);
  556.     
  557.     gRefSpeech->selStart = selStart;        /* Save user Selection */
  558.     gRefSpeech->selEnd = selEnd;
  559.     gRefSpeech->callbacks = callbacks;
  560.     gRefSpeech->w = w;
  561.     gRefSpeech->d = nil;
  562.     gRefSpeech->done = false;
  563.     
  564.     if (selStart == selEnd) {            /* No text select - select whole document */
  565.         selStart = 0;
  566.         selEnd = size;
  567.         }
  568.         
  569.     size = selEnd - selStart;
  570.     if (size == 0) {    
  571.         SysBeep(0);
  572.         goto CleanExit;
  573.         }    /* No Text - Beep... */
  574.     
  575.     gRefSpeech->TTSParams.flags = ttsMaleVoices | ttsFemaleVoices;
  576.     gRefSpeech->TTSParams.rate = 0;        /* use default pitch, rate, modulation */
  577.     gRefSpeech->TTSParams.pitch = 0;
  578.     gRefSpeech->TTSParams.modulation = -1;
  579.     
  580.     err = DoSetTTS(gRefSpeech);            /* If command key is press no selection */
  581.         
  582.     err = NewSpeechChannel(&gRefSpeech->TTSParams.curVoice.voice, &gRefSpeech->channel);    /* Open a SpeechChannel */
  583.     
  584.     if (err != noErr) {                    /* Could not open SpeechChannel: exiting.    */
  585.         callbacks->ReportOSError(err);
  586.         SysBeep(0);
  587.         goto CleanExit;
  588.         }
  589.     
  590.     SetTalkOptions(gRefSpeech);            /* setup callbacks and speaking options */
  591.  
  592.     if (CommandDown())    {                /* Messy fix so scrolling into VIEW the SELECTED WORD updates */
  593.         Handle h;                        /* so allow user to override if command key is pressed down */
  594.     
  595.         SetPort(w);
  596.         callbacks->SetSelection(selStart, selStart, -1);
  597.     
  598.         h = callbacks->Copy();        
  599.         callbacks->Paste(h);
  600.         DisposHandle(h);
  601.         }
  602.     
  603.     HandleDialog(text, selStart, size);    /* Go play the Text */
  604.  
  605.     DisposeSpeechChannel(gRefSpeech->channel);    /* clean up BEFORE EXITING. */
  606.     
  607. CleanExit:    
  608.     SetPort(w);                            /* Restore user Selection */
  609.     callbacks->SetSelection(gRefSpeech->selStart, gRefSpeech->selEnd, -1);    
  610.     SetPort(save_port);
  611.     DisposHandle(h);
  612.     RestoreA4();
  613. }    /* End of Main() */
  614.